// Copyright (c) 2009 http://grommet.codeplex.com
// This source is subject to the Microsoft Public License.
// See http://www.opensource.org/licenses/ms-pl.html
// All other rights reserved.

using System;
using System.Linq;
using Microsoft.SPOT;
using Grommet.Ext;
using System.Collections;
using Microsoft.SPOT.Hardware;

namespace Grommet.Linq.Test
{
    class Pet
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public bool Vaccinated { get; set; }
    }

    class Person
    {
        public string LastName { get; set; }
        public Pet[] Pets { get; set; }
    }

    public class Product
    {
        public string Name { get; set; }
        public int Code { get; set; }
    }

    public class LinqTest
    {
        public void Assert(bool result, string description)
        {
            if (result == false)
            {
                if (!description.IsNullOrEmpty())
                {
                    Debug.Print("Test failed check: " + description);
                }
                throw new Exception();
            }
        }

        public void Assert(bool result)
        {
            Assert(result, null);
        }

        public void AssertStringArray(IEnumerable results, string[] expected)
        {
            AssertStringArray(results, expected, null);
        }

        public void AssertStringArray(IEnumerable results, string[] expected, string description)
        {
            int index = 0;
            foreach (string result in results)
            {
                Assert(result == (string)expected[index++], description);
            }
            // Check again to make sure the enumerator is functioning correctly
            index = 0;
            foreach (string result in results)
            {
                Assert(result == (string)expected[index++], description);
            }
            Assert(index == expected.Length, description);
        }

        public void AggregateTest()
        {
            string sentence = "the quick brown fox jumps over the lazy dog";
            string expected = "dog lazy the over jumps fox brown quick the";

            // Split the string into individual words.
            string[] words = sentence.Split(' ');

            // Prepend each word to the beginning of the 
            // new sentence to reverse the word order.
            string reversed = (string)words.Aggregate((workingSentence, next) =>
                                                  next + " " + workingSentence);
            Assert(reversed == expected, "String not reversed");
        }

        public void Aggregate2Test()
        {
            int[] ints = { 4, 8, 8, 3, 9, 0, 7, 8, 2 };
            int expected = 6;

            // Count the even numbers in the array, using a seed value of 0.
            int numEven = (int)ints.Aggregate(0, (total, next) =>
                                                (int)next % 2 == 0 ? (int)total + 1 : total);

            Assert(numEven == expected);
        }

        public void Aggregate3Test()
        {
            string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };
            string expected = "PASSIONFRUIT";
            // Determine whether any string in the array is longer than "banana".
            string longestName =
                (string)fruits.Aggregate("banana",
                                (longest, next) =>
                                    ((string)next).Length > ((string)longest).Length ? next : longest,
                // Return the final result as an upper case string.
                                fruit => ((string)fruit).ToUpper());

            Assert(longestName == expected);
        }

        public void AllTest()
        {
            // Create an array of Pets.
            Pet[] pets = { new Pet { Name="Barley", Age=10 },
                   new Pet { Name="Boots", Age=4 },
                   new Pet { Name="Whiskers", Age=6 } };
            bool expected = false;

            // Determine whether all pet names 
            // in the array start with 'B'.
            bool allStartWithB = pets.All(pet => ((Pet)pet).Name.StartsWith("B"));

            Assert(allStartWithB == expected);
        }

        public void AnyTest()
        {
            ArrayList numbers = new ArrayList { 1, 2 };
            bool expected = true;

            bool hasElements = numbers.Any();

            Assert(hasElements == expected);
        }

        public void Any2Test()
        {
            ArrayList people = new ArrayList
                { new Person { LastName = "Haas",
                               Pets = new Pet[] { new Pet { Name="Barley", Age=10 },
                                                  new Pet { Name="Boots", Age=14 },
                                                  new Pet { Name="Whiskers", Age=6 }}},
                  new Person { LastName = "Fakhouri",
                               Pets = new Pet[] { new Pet { Name = "Snowball", Age = 1}}},
                  new Person { LastName = "Antebi",
                               Pets = new Pet[] { }},
                  new Person { LastName = "Philips",
                               Pets = new Pet[] { new Pet { Name = "Sweetie", Age = 2},
                                                  new Pet { Name = "Rover", Age = 13}} }
                };

            string[] expected = new string[] { "Haas", "Fakhouri", "Philips" };

            // Determine which people have a non-empty Pet array.
            IEnumerable names = from person in people
                                where ((Person)person).Pets.Any()
                                select ((Person)person).LastName;


            AssertStringArray(names, expected);
        }

        public void Any3Test()
        {
            // Create an array of Pets.
            Pet[] pets =
                { new Pet { Name="Barley", Age=8, Vaccinated=true },
                  new Pet { Name="Boots", Age=4, Vaccinated=false },
                  new Pet { Name="Whiskers", Age=1, Vaccinated=false } };
            bool expected = true;

            // Determine whether any pets over age 1 are also unvaccinated.
            bool unvaccinated =
                pets.Any(p => ((Pet)p).Age > 1 && ((Pet)p).Vaccinated == false);

            Assert(unvaccinated == expected);
        }

        private Pet[] GetCats()
        {
            Pet[] cats = { new Pet { Name="Barley", Age=8 },
                   new Pet { Name="Boots", Age=4 },
                   new Pet { Name="Whiskers", Age=1 } };
            return cats;
        }

        private Pet[] GetDogs()
        {
            Pet[] dogs = { new Pet { Name="Bounder", Age=3 },
                   new Pet { Name="Snoopy", Age=14 },
                   new Pet { Name="Fido", Age=9 } };
            return dogs;
        }

        public void Concat1Test()
        {
            Pet[] cats = GetCats();
            Pet[] dogs = GetDogs();

            string[] expected = new string[] { "Barley", "Boots", "Whiskers", "Bounder", "Snoopy", "Fido" };

            IEnumerable query =
                cats.Select(cat => ((Pet)cat).Name).Concat(dogs.Select(dog => ((Pet)dog).Name));

            AssertStringArray(query, expected);
        }

        public void Contains1Test()
        {
            string[] fruits = { "apple", "banana", "mango", "orange", "passionfruit", "grape" };
            string fruit = "mango";
            bool expected = true;

            bool hasMango = fruits.Contains(fruit);

            Assert(hasMango == expected);
        }

        // Custom comparer for the Product class.
        class ProductComparer : IEqualityComparer
        {
            // Products are equal if their names and product numbers are equal.
            public new bool Equals(object x, object y)
            {

                // Check whether the compared objects reference the same data.
                if (Object.ReferenceEquals(x, y))
                    return true;

                // Check whether any of the compared objects is null.
                if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                    return false;

                // Check whether the products' properties are equal.
                return ((Product)x).Code == ((Product)y).Code && ((Product)x).Name == ((Product)y).Name;
            }

            // If Equals() returns true for a pair of objects,
            // GetHashCode must return the same value for these objects.

            public int GetHashCode(object value)
            {
                Product product = value as Product;

                // Check whether the object is null.
                if (Object.ReferenceEquals(product, null))
                    return 0;

                // Get the hash code for the Name field if it is not null.
                int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();

                // Get the hash code for the Code field.
                int hashProductCode = product.Code.GetHashCode();

                // Calculate the hash code for the product.
                return hashProductName ^ hashProductCode;
            }

        }

        public void Contains2Test()
        {
            Product[] fruits = { new Product { Name = "apple", Code = 9 }, 
                       new Product { Name = "orange", Code = 4 }, 
                       new Product { Name = "lemon", Code = 12 } };

            Product apple = new Product { Name = "apple", Code = 9 };
            Product kiwi = new Product { Name = "kiwi", Code = 8 };

            ProductComparer prodc = new ProductComparer();

            bool expectedApple = true;
            bool expectedKiwi = false;

            bool hasApple = fruits.Contains(apple, prodc);
            bool hasKiwi = fruits.Contains(kiwi, prodc);

            Assert(hasApple == expectedApple);
            Assert(hasKiwi == expectedKiwi);
        }

        public void Count1Test()
        {
            string[] fruits = { "apple", "banana", "mango", "orange", "passionfruit", "grape" };
            int expected = 6;

            int numberOfFruits = fruits.Count();

            Assert(numberOfFruits == expected);
        }

        public void Count2Test()
        {
            Pet[] pets = { new Pet { Name="Barley", Vaccinated=true },
                   new Pet { Name="Boots", Vaccinated=false },
                   new Pet { Name="Whiskers", Vaccinated=false } };
            int expected = 2;

            int numberUnvaccinated = pets.Count(p => ((Pet)p).Vaccinated == false);

            Assert(numberUnvaccinated == expected);
        }

        public void Count3Test()
        {
            string[] fruits = { "apple", "banana", "mango", "orange", "passionfruit", "grape" };
            int expected = 6;

            fruits.Count();
            fruits.Count();
            int numberOfFruits = fruits.Count(); // see if count can be called multiple times

            Assert(numberOfFruits == expected);
        }

        public void DefaultIfEmpty1Test()
        {
            ArrayList pets =
                new ArrayList{ new Pet { Name="Barley", Age=8 },
                       new Pet { Name="Boots", Age=4 },
                       new Pet { Name="Whiskers", Age=1 } };
            string[] expected = new string[] { "Barley", "Boots", "Whiskers" };

            IEnumerable query = pets.DefaultIfEmpty();

            int index = 0;
            foreach(Pet pet in query)
            {
                Assert(pet.Name == expected[index++]);
            }
            Assert(index == expected.Length);
        }

        public void DefaultIfEmpty2Test()
        {
            Pet defaultPet = new Pet { Name = "Default Pet", Age = 0 };

            ArrayList pets1 =
                new ArrayList{ new Pet { Name="Barley", Age=8 },
                       new Pet { Name="Boots", Age=4 },
                       new Pet { Name="Whiskers", Age=1 } };
            string[] expected1 = new string[] { "Barley", "Boots", "Whiskers"};
            

            IEnumerable query1 = pets1.DefaultIfEmpty(defaultPet);

            int index = 0;
            foreach(Pet pet in query1)
            {
                Assert(pet.Name == expected1[index++]);
            }
            Assert(index == expected1.Length);

            ArrayList pets2 = new ArrayList();
            string[] expected2 = new string[] { "Default Pet" };

            IEnumerable query2 = pets2.DefaultIfEmpty(defaultPet);

            index = 0;
            foreach (Pet pet in query2)
            {
                Assert(pet.Name == expected2[index++]);
            }
            Assert(index == expected2.Length);

        }

        public void ElementAtTest()
        {
            string[] names =
                { "Hartono, Tommy", "Adams, Terry", "Andersen, Henriette Thaulow", 
                    "Hedlund, Magnus", "Ito, Shu" };
            int index = 1;
            string expected = "Adams, Terry";

            string result = (string)names.ElementAt(index);

            Assert(result == expected);
        }

        public void ElementAtOrDefaultTest()
        {
            string[] names =
                { "Hartono, Tommy", "Adams, Terry", "Andersen, Henriette Thaulow",
                    "Hedlund, Magnus", "Ito, Shu" };
            int index = 20;
            string expected = null;

            string name = (string)names.ElementAtOrDefault(index);

            Assert(name == expected);
        }

        public void EmptyTest()
        {
            string[] names1 = { "Hartono, Tommy" };

            string[] names2 = { "Adams, Terry", "Andersen, Henriette Thaulow",
                      "Hedlund, Magnus", "Ito, Shu" };

            string[] names3 = { "Solanki, Ajay", "Hoeing, Helge",
                      "Andersen, Henriette Thaulow",
                      "Potra, Cristina", "Iallo, Lucio" };

            ArrayList namesList = new ArrayList { names1, names2, names3 };
            string[] expected = new string[] { 
                "Adams, Terry",
                "Andersen, Henriette Thaulow", 
                "Hedlund, Magnus", 
                "Ito, Shu", 
                "Solanki, Ajay",
                "Hoeing, Helge", 
                "Andersen, Henriette Thaulow", 
                "Potra, Cristina", 
                "Iallo, Lucio"
                };
            // Only include arrays that have four or more elements
            IEnumerable allNames =
                (IEnumerable)namesList.Aggregate(Enumerable.Empty(),
                (current, next) => ((string[])next).Length > 3 ? ((IEnumerable)current).Concat((string[])next) : current);

            AssertStringArray(allNames, expected);
        }

        public void FirstTest()
        {
            int[] numbers = { 9, 34, 65, 92, 87, 435, 3, 54, 
                    83, 23, 87, 435, 67, 12, 19 };
            int expected = 9;

            int first = (int)numbers.First();

            Assert(first == expected);
        }

        public void First2Test()
        {
            int[] numbers = { 9, 34, 65, 92, 87, 435, 3, 54, 
                    83, 23, 87, 435, 67, 12, 19 };
            int expected = 92;

            int first = (int)numbers.First(number => ((int)number) > 80);

            Assert(first == expected);
        }

        public void LastTest()
        {
            int[] numbers = { 9, 34, 65, 92, 87, 435, 3, 54, 
                    83, 23, 87, 67, 12, 19 };
            int expected = 19;

            int first = (int)numbers.Last();

            Assert(first == expected);
        }

        public void Last2Test()
        {
            int[] numbers = { 9, 34, 65, 92, 87, 435, 3, 54, 
                    83, 23, 87, 67, 12, 19 };
            int expected = 87;

            int first = (int)numbers.Last(number => ((int)number) > 80);

            Assert(first == expected);
        }

        public void OfTypeTest()
        {
            ArrayList fruits = new ArrayList();
            fruits.Add("Mango");
            fruits.Add("Orange");
            fruits.Add("Apple");
            fruits.Add(3.0);
            fruits.Add("Banana");

            // Apply OfType() to the ArrayList.
            string[] expected1 = new string[] { "Mango", "Orange", "Apple", "Banana" };
            IEnumerable query1 = fruits.OfType(typeof(string));

            AssertStringArray(query1, expected1);

            // The following query shows that the standard query operators such as 
            // Where() can be applied to the ArrayList type after calling OfType().
            string[] expected2 = new string[] { "Mango", "Orange", "Banana" };
            IEnumerable query2 =
                fruits.OfType(typeof(string)).Where(fruit => ((string)fruit).ToLower().Contains("n"));

            AssertStringArray(query2, expected2);
        }

        private class IntComparer : IComparer
        {
            #region IComparer Members

            public int Compare(object x, object y)
            {
                return (int)x - (int)y;
            }

            #endregion
        }

        private class StringComparer : IComparer
        {
            #region IComparer Members

            public int Compare(object x, object y)
            {
                return string.Compare((string)x, (string)y);
            }

            #endregion
        }

        public void OrderBy1Test()
        {
            Pet[] pets = { new Pet { Name="Barley", Age=8 },
                   new Pet { Name="Boots", Age=4 },
                   new Pet { Name="Whiskers", Age=1 } };

            string[] expected = new string[] { "Whiskers", "Boots", "Barley" };

            IEnumerable query = pets.OrderBy(pet => ((Pet)pet).Age, new IntComparer());

            int index = 0;
            foreach (Pet pet in query)
            {
                Assert(pet.Name == expected[index++]);
            }
            Assert(index == expected.Length);
        }

        /// <summary>
        /// This IComparer class sorts by the fractional part of the decimal number.
        /// </summary>
        public class SpecialComparer : IComparer
        {
            /// <summary>
            /// Compare two decimal numbers by their fractional parts.
            /// </summary>
            /// <param name="d1">The first decimal to compare.</param>
            /// <param name="d2">The second decimal to compare.</param>
            /// <returns>1 if the first decimal's fractional part 
            /// is greater than the second decimal's fractional part,
            /// -1 if the first decimal's fractional
            /// part is less than the second decimal's fractional part,
            /// or the result of calling Decimal.Compare()
            /// if the fractional parts are equal.</returns>
            public int Compare(object d1, object d2)
            {
                double fractional1, fractional2;

                // Get the fractional part of the first number.
                try
                {
                    fractional1 = (double)d1 - System.Math.Floor((double)d1);
                }
                catch (Exception)
                {
                    fractional1 = (double)d1;
                }
                // Get the fractional part of the second number.
                try
                {
                    fractional2 = (double)d2 - System.Math.Floor((double)d2);
                }
                catch (Exception)
                {
                    fractional2 = (double)d2;
                }

                if (fractional1 == fractional2)
                {
                    return ((double)d1 < (double)d2) ? -1 : ((double)d1 > (double)d2) ? 1 : 0;
                }
                else if (fractional1 > fractional2)
                    return 1;
                else
                    return -1;
            }
        }

        public void OrderByDescending1Test()
        {
            ArrayList doubles =
                new ArrayList { 6.2, 8.3, 0.5, 1.3, 6.3, 9.7 };
            double[] expected = new double[] { 9.7, 0.5, 8.3, 1.3, 6.3, 6.2};
            IEnumerable query =
                doubles.OrderByDescending(num => num, new SpecialComparer());

            int index = 0;
            foreach (double d in query)
            {
                Assert(d == (double)expected[index++]);
            }
            Assert(index == expected.Length);
        }

        public void RangeTest()
        {
            // Generate a sequence of integers from 1 to 10 
            // and then select their squares.
            IEnumerable squares = Enumerable.Range(1, 10).Select(x => (int)x * (int)x);

            int index = 1;
            foreach (int num in squares)
            {
                Assert(num == index * index);
                index++;
            }
            Assert(index == 11);
        }

        public void RepeatTest()
        {
            IEnumerable strings = Enumerable.Repeat("I like programming.", 15);

            Assert(strings.Count() == 15);
        }

        public void ReverseTest()
        {
            char[] apple = { 'a', 'p', 'p', 'l', 'e' };
            char[] expected = { 'e', 'l', 'p', 'p', 'a' };

            IEnumerable reversed = apple.Reverse().ToArray();

            int index = 0;
            foreach (char chr in reversed)
            {
                Assert(chr == (char)expected[index++]);
            }
            Assert(index == expected.Length);
        }

        public void ThenByTest()
        {
            string[] fruits = { "grape", "passionfruit", "banana", "mango", 
                      "orange", "raspberry", "apple", "blueberry" };

            string[] expected = { "apple", "grape", "mango", "banana", "orange", 
                                    "blueberry", "raspberry", "passionfruit" };

            // Sort the strings first by their length and then 
            //alphabetically by passing the identity selector function.
            IEnumerable query =
                fruits.OrderBy(fruit => ((string)fruit).Length, new IntComparer()).ThenBy(fruit => fruit, new StringComparer());

            int index = 0;
            foreach (string fruit in query)
            {
                Assert(fruit == (string)expected[index++]);
            }
            Assert(index == expected.Length);
        }

        public class CaseInsensitiveComparer : IComparer
        {
            public int Compare(object x, object y)
            {
                return string.Compare(((string)x).ToLower(), ((string)y).ToLower());
            }
        }

        public void ThenByDescendingEx1Test()
        {
            string[] fruits = { "apPLe", "baNanA", "apple", "APple", "orange", "BAnana", "ORANGE", "apPLE" };
            string[] expected = { "apPLe", "apple", "APple", "apPLE", "orange", "ORANGE", "baNanA", "BAnana" };

            // Sort the strings first ascending by their length and 
            // then descending using a custom case insensitive comparer.
            IEnumerable query =
                fruits
                .OrderBy(fruit => ((string)fruit).Length, new IntComparer())
                .ThenByDescending(fruit => fruit, new CaseInsensitiveComparer());

            AssertStringArray(query, expected);
        }

        public void WhereTest()
        {
            ArrayList fruits =
                new ArrayList { "apple", "passionfruit", "banana", "mango", 
                    "orange", "blueberry", "grape", "strawberry" };
            string[] expected = new string[] { "apple", "mango", "grape" };

            IEnumerable query = fruits.Where(fruit => ((string)fruit).Length < 6);

            AssertStringArray(query, expected);
        }

        public void Where2Test()
        {
            ArrayList fruits =
                new ArrayList { "apple", "passionfruit", "banana", "mango", 
                    "orange", "blueberry", "grape", "strawberry" };
            string[] expected = new string[] { "apple", "mango", "grape" };
            int expectedCount = expected.Length;

            IEnumerable query = fruits.Where(fruit => ((string)fruit).Length < 6);

            query.Count();
            int actualCount = query.Count();

            AssertStringArray(query, expected);
            Assert(actualCount == expectedCount);
        }

        public class RssFeed
        {
            public string Name { get; set; }
            public string Url { get; set; }
            public DateTime LastUpdate { get; set; }
        }

        public void FeedTest()
        {
            ArrayList feedList = new ArrayList()
            {
                new RssFeed() { 
                    Name = "XBox Live's Major Nelson", 
                    Url="http://feeds.feedburner.com/MajorNelson",
                    LastUpdate = new DateTime(2009, 03, 27) },
                new RssFeed() { 
                    Name = "Quirks & Quarks", 
                    Url="http://www.cbc.ca/technology/quirks-blog/index.xml",
                    LastUpdate = new DateTime(2009, 03, 28) },
                new RssFeed() { 
                    Name = "Dilbert Daily Strip", 
                    Url="http://feeds.feedburner.com/DilbertDailyStrip",
                    LastUpdate = new DateTime(2009, 03, 27) },
                new RssFeed() { 
                    Name = "MSDN Just Published", 
                    Url="http://msdn.microsoft.com/rss.xml",
                    LastUpdate = new DateTime(2009, 03, 19) },
            };

            var feedsToUpdate = from feed in feedList
                                orderby ((RssFeed)feed).LastUpdate descending, ((RssFeed)feed).Name 
                                where ((RssFeed)feed).LastUpdate > new DateTime(2009, 03, 20)
                                select (((RssFeed)feed).Name);

            foreach (string name in feedsToUpdate)
            {
                Debug.Print(name);
            }
        }
    }
}
